/**
 *
 */
package org.workplicity.marist.grounds.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.workplicity.entry.Entry;
import org.workplicity.entry.User;
import org.workplicity.util.Helper;
import org.workplicity.util.WorkDate;
import org.workplicity.worklet.WorkletContext;

/**
 *
 * @author Brian J. Herbert
 */
public class Repair {

    private static WorkletContext context;
    private static ArrayList<Entry> dirtyUsers;
    private static ArrayList<Entry> dirtyRequests;
    private static ArrayList<Entry> dirtyWorkSlates;
    private static ArrayList<Entry> dirtyServices;
    private static ArrayList<Entry> dirtyFutures;
    public static long startDate;
    public static long endDate;
    public static final int ALL = 0;
    public static final int ACCOUNTS = 1;
    public static final int CLEARINGHOUSE = 2;
    public static final int WORK_SLATES = 3;
    public static final int SERVICES = 4;
    public static final int FUTURES = 5;
    public static final int FIX_INVALID_CREATE_DATE = 0;
    public static final int FIX_INVALID_UPDATE_DATE = 1;
    public static final int FIX_INVALID_UPDATE_USER = 2;
    public static final int FIX_INVALID_WORK_SLATE_REFERENCES = 3;
    public static final int FIX_INVALID_SERVICE_REFERENCES = 4;
    public static final int FIX_DUPLICATE_WORK_SLATE_REFERENCES = 5;
    public static final int FIX_DUPLICATE_SERVICE_REFERENCES = 6;

    /**
     * setGlobals()
     * Sets the worklet context for the class and initializes the temporary holding arrays for dirty
     * entries.  Takes no arguments and returns nothing.
     */
    public static void setGlobals(long start, long end) {
        context = WorkletContext.getInstance();
        dirtyUsers = new ArrayList<Entry>();
        dirtyRequests = new ArrayList<Entry>();
        dirtyWorkSlates = new ArrayList<Entry>();
        dirtyServices = new ArrayList<Entry>();
        dirtyFutures = new ArrayList<Entry>();

        startDate = start;
        endDate = end;
    }

    /**
     * fixInvalidCreateDate:
     * Iterates through all the entries in an ArrayList (queried from one or more repositories) and
     * fixes all invalid create dates (e.g., those less than 0 or those set in the future) by resetting
     * them to the current date.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixInvalidCreateDate(ArrayList<Entry> specEntries, int repos) {
        ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
        ArrayList<Entry> dirties = null;
        String reporterString = "Entry ";

        switch (repos) {
            case ACCOUNTS:
                dirties = dirtyUsers;
                reporterString = "User ";
                break;

            case CLEARINGHOUSE:
                dirties = dirtyRequests;
                reporterString = "Request ";
                break;

            case WORK_SLATES:
                dirties = dirtyWorkSlates;
                reporterString = "Work slate ";
                break;

            case SERVICES:
                dirties = dirtyServices;
                reporterString = "Service ";
                break;

            case FUTURES:
                dirties = dirtyFutures;
                reporterString = "Future request ";
                break;

            default:
                return specEntries;
        }

        WorkDate today = new WorkDate(new Date());
//      Constructs a Date object (which will automatically set it to the current time/date to the
//      nearst milli, then uses that date to construct a WorkDate for comparisons to existing dates.

        for (int i = 0; i < specEntries.size(); i++) {
            Entry thisEntry = specEntries.get(i);
            WorkDate createDate = thisEntry.getCreateDate();

//              Fix the current entry if its create date has somehow become negative.
            if (createDate.getTime() < 0) {
                thisEntry.setCreateDate(today);
                thisMethodsDirties.add(thisEntry);
            } //          Fix the current entry if its create date is a future date.
            else if (createDate.getTime() > today.getTime()) {
                thisEntry.setCreateDate(today);
                thisMethodsDirties.add(thisEntry);
            }

//              Replaces the potentially oudated copy of the entry in our local ArrayList with the up-to-
//              date copy that has been modified locally
            specEntries.remove(i);
            specEntries.add(i, thisEntry);
        }

        if (thisMethodsDirties.size() > 0) {
            for (int i = 0; i < thisMethodsDirties.size(); i++) {
                Entry thisDirty = thisMethodsDirties.get(i);
                Report.add(Report.alertType.invalidCreateDate, reporterString + thisDirty.getId()
                        + " has an invalid create date");
//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                for (int j = 0; j < dirties.size(); j++) {
                    Entry thisGlobalDirty = dirties.get(j);
                    if (thisDirty.getClass() == thisGlobalDirty.getClass()
                            && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                        dirties.remove(j);
                    }
                }

                dirties.add(thisDirty);
            }
        }

        return specEntries;
    }

    /**
     * fixInvalidUpdateDate:
     * Iterates through all the entries in an ArrayList (queried from one or more repositories) and
     * fixes all invalid update dates (e.g., those less than 0 or those set in the future) by resetting
     * them to the current date.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixInvalidUpdateDate(ArrayList<Entry> specEntries,
            int repos) {
        ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
        ArrayList<Entry> dirties = null;
        String reporterString = "Entry ";

        switch (repos) {
            case ACCOUNTS:
                dirties = dirtyUsers;
                reporterString = "User ";
                break;

            case CLEARINGHOUSE:
                dirties = dirtyRequests;
                reporterString = "Request ";
                break;

            case WORK_SLATES:
                dirties = dirtyWorkSlates;
                reporterString = "Work slate ";
                break;

            case SERVICES:
                dirties = dirtyServices;
                reporterString = "Service ";
                break;

            case FUTURES:
                dirties = dirtyFutures;
                reporterString = "Future request ";
                break;

            default:
                return specEntries;
        }

        WorkDate today = new WorkDate(new Date());
//      Constructs a Date object (which will automatically set it to the current time/date to the
//      nearst milli, then uses that date to construct a WorkDate for comparisons to existing dates.

        for (int i = 0; i < specEntries.size(); i++) {
            Entry thisEntry = specEntries.get(i);
            WorkDate updateDate = thisEntry.getUpdateDate();

//              Fix the current entry if its update date is null for some reason.
            if (updateDate == null) {
                thisEntry.setUpdateDate(today);
                thisMethodsDirties.add(thisEntry);
            } else {
//                  Fix the current entry if its update date has somehow become negative.
                if (updateDate.getTime() < 0) {
                    thisEntry.setUpdateDate(today);
                    thisMethodsDirties.add(thisEntry);
                } //                  Fix the current entry if its update date is a future date.
                else if (updateDate.getTime() > today.getTime()) {
                    thisEntry.setUpdateDate(today);
                    thisMethodsDirties.add(thisEntry);
                }
            }

            specEntries.remove(i);
            specEntries.add(i, thisEntry);
        }

        if (thisMethodsDirties.size() > 0) {
            for (int i = 0; i < thisMethodsDirties.size(); i++) {
                Entry thisDirty = thisMethodsDirties.get(i);
                Report.add(Report.alertType.invalidUpdateDate, reporterString + thisDirty.getId()
                        + " has an invalid update date");
//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                for (int j = 0; j < dirties.size(); j++) {
                    Entry thisGlobalDirty = dirties.get(j);
                    if (thisDirty.getClass() == thisGlobalDirty.getClass()
                            && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                        dirties.remove(j);
                    }
                }

                dirties.add(thisDirty);
            }
        }

        return specEntries;
    }

    /**
     * fixInvalidUpdateUser:
     * Iterates through all the entries in an ArrayList (queried from one or more repositories) and
     * retrieves the update user from each, then checks it against the
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param accounts The list of all entries in the Accounts repository, for verifying user IDs
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixInvalidUpdateUser(ArrayList<Entry> specEntries,
            ArrayList<Entry> accounts, int repos) {
        ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
        ArrayList<Entry> dirties = null;
        String reporterString = "Entry ";

        switch (repos) {
            case ACCOUNTS:
                dirties = dirtyUsers;
                reporterString = "User ";
                break;

            case CLEARINGHOUSE:
                dirties = dirtyRequests;
                reporterString = "Request ";
                break;

            case WORK_SLATES:
                dirties = dirtyWorkSlates;
                reporterString = "Work slate ";
                break;

            case SERVICES:
                dirties = dirtyServices;
                reporterString = "Service ";
                break;

            case FUTURES:
                dirties = dirtyFutures;
                reporterString = "Future request ";
                break;

            default:
                return specEntries;
        }

        for (int i = 0; i < specEntries.size(); i++) {
            Entry thisEntry = specEntries.get(i);

            if (thisEntry.getUpdateDate().getTime() >= startDate
                    && thisEntry.getUpdateDate().getTime() <= endDate) {
                Integer updateUser = thisEntry.getUpdateUserId();
                int numUsers = 0;

                for (int j = 0; j < accounts.size(); j++) {
                    if (updateUser.equals(accounts.get(j).getId())) {
                        numUsers++;
                    }
                }

                if (numUsers == 0) {
                    thisEntry.setUpdateUserId(Helper.whoAmI(context).getId());
                    thisMethodsDirties.add(thisEntry);
                }
            }
        }

        if (thisMethodsDirties.size() > 0) {
            for (int i = 0; i < thisMethodsDirties.size(); i++) {
                Entry thisDirty = thisMethodsDirties.get(i);
                Report.add(Report.alertType.invalidUpdateUser, reporterString + thisDirty.getId()
                        + " has an invalid update user\n" + startDate + " <= "
                        + thisDirty.getUpdateDate().getTime() + " <= " + endDate);
//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                for (int j = 0; j < dirties.size(); j++) {
                    Entry thisGlobalDirty = dirties.get(j);
                    if (thisDirty.getClass() == thisGlobalDirty.getClass()
                            && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                        dirties.remove(j);
                    }
                }

                dirties.add(thisDirty);
            }
        }

        return specEntries;
    }

    /**
     * fixInvalidWorkSlateReferences:
     * For User entries (the Accounts repository) only.
     * Looks at each user's work slate list and verifies the existence of each work slate on the list.
     * If an id for a nonexistent work slate is found on the list, that id will be removed from the
     * list to repair the problem.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param workSlates The list of all entries in the Work Slates repository, for verifying work
     * slate IDs
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixInvalidWorkSlateReferences(ArrayList<Entry> specEntries,
            ArrayList<Entry> workSlates,
            int repos) {
        if (repos == ACCOUNTS) {

            ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
            ArrayList<Entry> dirties = dirtyUsers;

            for (int i = 0; i < specEntries.size(); i++) {
                boolean dirty = false;
                User thisUser = (User) specEntries.get(i);

                if (thisUser.getUpdateDate().getTime() >= startDate
                        && thisUser.getUpdateDate().getTime() <= endDate) {
                    List<Integer> userSlates = thisUser.getWorkSlateList();

                    for (int j = 0; j < userSlates.size(); j++) {
                        Integer thisSlate = userSlates.get(j);
                        int numSlates = 0;

                        for (int k = 0; k < workSlates.size(); k++) {
                            if (thisSlate.equals(workSlates.get(k).getId())) {
                                numSlates++;
                            }
                        }

                        if (numSlates == 0) {
                            dirty = true;
                            userSlates.remove(j--);
//                      j--;
                            Report.add(Report.alertType.badWorkSlateId, "Work slate " + thisSlate
                                    + " on user " + thisUser.getId() + " does not exist.");
//                  Decrement j because otherwise, if an id is removed, the next id will be skipped
                        }
                    }

                    if (dirty) {
                        thisUser.setWorkSlateList(userSlates);
                        thisMethodsDirties.add(thisUser);
                    }
                }
            }

            if (thisMethodsDirties.size() > 0) {
                for (int i = 0; i < thisMethodsDirties.size(); i++) {
                    Entry thisDirty = thisMethodsDirties.get(i);

//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                    for (int j = 0; j < dirties.size(); j++) {
                        Entry thisGlobalDirty = dirties.get(j);
                        if (thisDirty.getClass() == thisGlobalDirty.getClass()
                                && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                            dirties.remove(j);
                        }
                    }

                    dirties.add(thisDirty);
                }
            }
        }
        return specEntries;
    }

    /**
     * fixInvalidServiceReferences:
     * For User entries (the Accounts repository) only.
     * Looks at each user's service list and verifies the existence of each service on the list.  If an
     * id for a nonexistent service is found on the list, that id will be removed from the list to
     * repair the problem.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param services The list of all entries in the Services repository, for verifying service IDs
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixInvalidServiceReferences(ArrayList<Entry> specEntries,
            ArrayList<Entry> services, int repos) {
        if (repos == ACCOUNTS) {

            ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
            ArrayList<Entry> dirties = dirtyUsers;

            for (int i = 0; i < specEntries.size(); i++) {
                boolean dirty = false;
                User thisUser = (User) specEntries.get(i);

                if (thisUser.getUpdateDate().getTime() >= startDate
                        && thisUser.getUpdateDate().getTime() <= endDate) {
                    List<Integer> userServices = thisUser.getServiceList();

                    for (int j = 0; j < userServices.size(); j++) {
                        Integer thisService = userServices.get(j);
                        int numServices = 0;

                        for (int k = 0; k < services.size(); k++) {
                            if (thisService.equals(services.get(k).getId())) {
                                numServices++;
                            }
                        }

                        if (numServices == 0) {
                            dirty = true;
                            userServices.remove(j--);
//                      Decrement j because otherwise, if an id is removed, the next id will be skipped
                            Report.add(Report.alertType.badServiceId, "Service " + thisService
                                    + " on user " + thisUser.getId() + " does not exist.");
                        }
                    }

                    if (dirty) {
                        thisUser.setServiceList(userServices);
                        thisMethodsDirties.add(thisUser);
                    }
                }
            }

            if (thisMethodsDirties.size() > 0) {
                for (int i = 0; i < thisMethodsDirties.size(); i++) {
                    Entry thisDirty = thisMethodsDirties.get(i);

//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                    for (int j = 0; j < dirties.size(); j++) {
                        Entry thisGlobalDirty = dirties.get(j);
                        if (thisDirty.getClass() == thisGlobalDirty.getClass()
                                && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                            dirties.remove(j);
                        }
                    }

                    dirties.add(thisDirty);
                }
            }
        }
        return specEntries;
    }

    /**
     * fixDuplicateWorkSlateReferences:
     * For User entries (the Accounts repository) only.
     * Looks at each user's work slate list and checks whether each id occurs elsewhere later in the
     * list.  If any work slate ids repeat, the duplicates will be removed from the list to repair the
     * problem.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @param repos The repository currently being checked (to determine where to place dirty entries)
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixDuplicateWorkSlateReferences(ArrayList<Entry> specEntries,
            int repos) {
        if (repos == ACCOUNTS) {
            /**
             * Only execute if combo box specifies Accounts
             */
            ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
            ArrayList<Entry> dirties = dirtyUsers;

            for (int i = 0; i < specEntries.size(); i++) {
                boolean dirty = false;
                User thisUser = (User) specEntries.get(i);

                if (thisUser.getUpdateDate().getTime() >= startDate
                        && thisUser.getUpdateDate().getTime() <= endDate) {
                    List<Integer> workSlates = thisUser.getWorkSlateList();

                    for (int j = 0; j < workSlates.size(); j++) {
                        Integer thisSlate = workSlates.get(j);

                        for (int k = 0; k < workSlates.size(); k++) {
                            if (j != k && thisSlate.equals(workSlates.get(k))) {
                                dirty = true;
                                workSlates.remove(k--);
//                          k--;
                                Report.add(Report.alertType.duplicateWorkSlate, "Work slate "
                                        + thisSlate + " on user " + thisUser.getId()
                                        + " is a duplicate.");
//                      Decrement k for the same reason as j above, and do not break from the loop; even
//                      if a duplicate is found, that doesn't guarantee it's the only duplicate.
                            }
                        }
                    }

                    if (dirty) {
                        thisUser.setWorkSlateList(workSlates);
                        thisMethodsDirties.add(thisUser);
                    }
                }
            }

            if (thisMethodsDirties.size() > 0) {
                for (int i = 0; i < thisMethodsDirties.size(); i++) {
                    Entry thisDirty = thisMethodsDirties.get(i);

//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                    for (int j = 0; j < dirties.size(); j++) {
                        Entry thisGlobalDirty = dirties.get(j);
                        if (thisDirty.getClass() == thisGlobalDirty.getClass()
                                && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                            dirties.remove(j);
                        }
                    }

                    dirties.add(thisDirty);
                }
            }
        }
        return specEntries;
    }

    /**
     * fixDuplicateServiceReferences:
     * For User entries (the Accounts repository) only.
     * Looks at each user's service list and checks whether each id occurs elsewhere later in the
     * list.  If any service ids repeat, the duplicates will be removed from the list to repair the
     * problem.
     *
     * @param specEntries The list of all entries from the repository/ies the user has requested to be
     * searched and repaired
     *
     * @return specEntries The list of all entries from the specified repository/ies, updated to
     * replace any entries that had to be updated with the updated versions of those entries
     */
    public static ArrayList<Entry> fixDuplicateServiceReferences(ArrayList<Entry> specEntries,
            int repos) {
        if (repos == ACCOUNTS) {
            ArrayList<Entry> thisMethodsDirties = new ArrayList<Entry>();
            ArrayList<Entry> dirties = dirtyUsers;

            for (int i = 0; i < specEntries.size(); i++) {
                boolean dirty = false;
                User thisUser = (User) specEntries.get(i);

                if (thisUser.getUpdateDate().getTime() >= startDate
                        && thisUser.getUpdateDate().getTime() <= endDate) {
                    List<Integer> services = thisUser.getServiceList();

                    for (int j = 0; j < services.size(); j++) {
                        Integer thisService = services.get(j);

                        for (int k = 0; k < services.size(); k++) {
                            if (j != k && thisService != services.get(k)) {
                                dirty = true;
                                services.remove(k--);
//                          k--;
                                Report.add(Report.alertType.duplicateService, "Service " + thisService
                                        + " on user " + thisUser.getId() + " is a duplicate.");
//                      Decrement k for the same reason a j above, and do not break from the loop; even
//                      if a duplicate is found, that doesn't guarantee it's the only duplicate.
                            }
                        }
                    }

                    if (dirty) {
                        thisUser.setServiceList(services);
                        thisMethodsDirties.add(thisUser);
                    }
                }
            }

            if (thisMethodsDirties.size() > 0) {
                for (int i = 0; i < thisMethodsDirties.size(); i++) {
                    Entry thisDirty = thisMethodsDirties.get(i);

//              Search the current global list of dirties for any outdated versions of newly updated
//              entries and remove them so they can be replaced with the up-to-date version.
                    for (int j = 0; j < dirties.size(); j++) {
                        Entry thisGlobalDirty = dirties.get(j);
                        if (thisDirty.getClass() == thisGlobalDirty.getClass()
                                && thisDirty.getId().equals(thisGlobalDirty.getId())) {
                            dirties.remove(j);
                        }
                    }

                    dirties.add(thisDirty);
                }
            }
        }
        return specEntries;
    }

    public static void writeBackDirties() {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                try {
                    for (int i = 0; i < dirtyUsers.size(); i++) {
                        if (!Helper.insert(dirtyUsers.get(i), "Accounts", context)) {
                            throw new Exception("Entry insert failed");
                        }
                    }

                    for (int i = 0; i < dirtyRequests.size(); i++) {
                        if (!Helper.insert(dirtyRequests.get(i), "Clearinghouse", context)) {
                            throw new Exception("Entry insert failed");
                        }
                    }

                    for (int i = 0; i < dirtyWorkSlates.size(); i++) {
                        if (!Helper.insert(dirtyWorkSlates.get(i), "WorkSlates", context)) {
                            throw new Exception("Entry insert failed");
                        }
                    }

                    for (int i = 0; i < dirtyServices.size(); i++) {
                        if (!Helper.insert(dirtyServices.get(i), "Services", context)) {
                            throw new Exception("Entry insert failed");
                        }
                    }

                    for (int i = 0; i < dirtyFutures.size(); i++) {
                        if (!Helper.insert(dirtyFutures.get(i), "Futures", context)) {
                            throw new Exception("Entry insert failed");
                        }
                    }
                } catch (Exception e) {
                    System.out.println(e);
                }

                JOptionPane.showMessageDialog(null, Report.getNumRepaired()
                        + " repair(s) have been performed and were added to the report", "Actions taken",
                        JOptionPane.WARNING_MESSAGE);
            }
        });
    }
}
